local duf = {
	virtual_class_handlers = {},
	class_handlers = {},
	function_handlers = {},
	function_events = {},
}

local function toliteral(s)
	local t = type(s)
	if t == "nil" then
		return "nil"
	elseif t == "string" then
		return string.format("%q", s)
	elseif t == "number" then
		return string.format("%s", s)
	elseif t == "boolean" then
		return s and "true" or "false"
	else
		error("invalid type in toliteral")
	end
end

function duf:GetImmediateValue(state, decl, s)
	if type(s) == "string" and string.find(s, "$", 1, true) then
		return s:gsub("$parent", "parent"):gsub("$arg(%d+)", function (value)
			state.max_arg = math.max(tonumber(value), state.max_arg or 0)
			return "arg"..value
		end)
	else
		return toliteral(s)
	end
end

function duf:AddEvent(state, decl, event, callback, unit)
	if unit then
		callback = ("function (event, unit, ...) if unit == %s then return %s(event, unit, ...) end end"):format(unit, callback)
	-- elseif event:find("^UNIT_") then
	end
	local events = decl.events[event]
	if not events then
		events = {}
		decl.events[event] = events
	end
	events[#events + 1] = callback
end

function duf:GetValue(state, decl, name, default, callback)
	local s = decl[name] or default
	if type(s) == "string" and string.find(s, "${", 1, true) then
		while true do
			local b, e, name = s:find("${([^}]+)}")
			if not b then break end
			local handler = self.function_handlers[name]
			if handler then
				name = handler(self, state, decl, name)
			end
			local events = self.function_events[name]
			if events then
				for _, event in ipairs(events) do
					self:AddEvent(state, decl, event, callback)
				end
			end
			s = s:sub(1, b - 1) .. name .. s:sub(e + 1)
		end
	end
	return self:GetImmediateValue(state, decl, s)
end

function duf:FrameGenerator(state, decl, add)
	local class = decl.class or "Frame" -- self:GetValue(decl, "class", "Frame")
	if not decl.events then
		decl.events = {}
	end
	local v = self.virtual_class_handlers[class]
	if v then
		v(self, state, decl, add)
	else
		if decl.parent then
			local f = loadstring("return "..self:GetValue(state, decl, "parent"))
			add("local parent = %s", f())
		end
		add("local frame = CreateFrame(%q, %s, parent, %s)", class, self:GetValue(state, decl, "name"), self:GetValue(state, decl, "template"))
	end
	if decl.movable ~= nil then
		add("frame:SetMovable(%s)", self:GetValue(state, decl, "movable"))
	end
	if decl.toplevel ~= nil then
		add("frame:SetToplevel(%s)", self:GetValue(state, decl, "toplevel"))
	end
	if decl.clique then
		add("frame:RegisterForClicks(\"LeftButtonUp\", \"RightButtonUp\", \"MiddleButtonUp\", \"Button4Up\", \"Button5Up\")")
		add("ClickCastFrames = ClickCastFrames or {}")
		add("ClickCastFrames[frame] = true")
	end
	if decl.attributes then
		for name, value in pairs(decl.attributes) do
			add("frame:SetAttribute(%s, %s)", toliteral(name), self:GetImmediateValue(state, decl, value))
		end
	end
	local handler = self.class_handlers[decl.class]
	if handler then
		handler(self, state, decl, add)
	end
	if next(decl.events) then
		for event, callbacks in pairs(decl.events) do
			for _, callback in ipairs(callbacks) do
				add("add_handler(%q, %s)", event, callback)
			end
		end
	end
	if decl.scripts then
		for event, handler in pairs(decl.scripts) do
			local f = loadstring("return "..self:GetValue(state, decl, handler))
			add("frame:SetScript(%q, %s)", event, f())
		end
	end
	if decl.anchors then
		add("frame:ClearAllPoints()")
		if decl.anchors == "all" then
			add("frame:SetAllPoints()")
		else
			for _, anchor in ipairs(decl.anchors) do
				add("frame:SetPoint(%s, %s, %s, %s, %s)",
					self:GetImmediateValue(state, decl, anchor[1]),
					self:GetImmediateValue(state, decl, anchor[2]),
					self:GetImmediateValue(state, decl, anchor[3]),
					self:GetImmediateValue(state, decl, anchor[4]),
					self:GetImmediateValue(state, decl, anchor[5]))
			end
		end
	end
	if decl.width then
		add("frame:SetWidth(%s)", self:GetValue(state, decl, "width"))
	end
	if decl.height then
		add("frame:SetHeight(%s)", self:GetValue(state, decl, "height"))
	end
	if decl.children then
		for name, value in pairs(decl.children) do
			add("do")
			add("\tlocal parent, child = frame")
			add("\tdo")
			self:FrameGenerator(state, value, function (s, ...) add("\t\t"..s, ...) end)
			add("\t\tchild = frame")
			add("\tend")
			add("\tframe.%s = child", name)
			add("end")
		end
	end
end

local HEADER = [[
return function (...)
	local handlers = {}
	local function add_handler(event, callback)
		local tbl = handlers[event]
		if not tbl then
			tbl = {}
			handlers[event] = tbl
		end
		tbl[#tbl + 1] = callback
	end
]]

local FOOTER = [[

	if next(handlers) then
		for event in pairs(handlers) do
			frame:RegisterEvent(event)
		end
		frame:SetScript("OnEvent", function (self, event, ...)
			local tbl = handlers[event]
			if not tbl then frame:UnregisterEvent(event) end
			for _, callback in ipairs(tbl) do
				callback(event, ...)
			end
		end)
	end
	return frame
end
]]

function duf:HandleStateData(t, state)
	if state.max_arg and state.max_arg > 0 then
		table.insert(t, 2, "local arg1 = (...)")
		for i = 2, state.max_arg do
			table.insert(t, 1+i, ("local arg%d = select(%d, ...)"):format(i, i))
		end
	end
	self:DeclareUtils(t, state)
end

function duf:Make(decl)
	local t = { HEADER, }
	local add = function (s, ...)
		t[#t + 1] = s:format(...)
	end
	local state = {}
	self:FrameGenerator(state, decl, add)
	self:HandleStateData(t, state)
	return table.concat(t, "\n\t")..FOOTER
end

function duf:AddCallback(state, decl, add, name, content)
	add("local function %s()", name)
	local add_sub = function (s, ...) add("\t"..s, ...) end
	if state.callback_checkisshown then
		add("\tif not frame:IsShown() then return end")
	end
	if state.callback_header then
		state.callback_header(state, decl, add_sub)
	end
	content(state, decl, add_sub)
	if state.callback_footer then
		state.callback_footer(state, decl, add_sub)
	end
	add("end")
end

_G.DeclarativeUnitFrames = duf
